/*******************************************************************************
 * Copyright 2011-2012 Dik Grapendaal
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package sh.grapendaal.tsuushin.socket;

import java.io.IOException;
import java.net.InetSocketAddress;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;

public class IrcSocket {
	public enum DisconnectReason {
		/**
		 * Indicates an unexpected disconnection.
		 */
		CONNECTION_RESET,

		/**
		 * Indicates a cleanly closed connection.
		 */
		CONNECTION_CLOSED
	}

	private InetSocketAddress server;
	private ClientBootstrap bootstrap;
	private Channel channel;
	private ChannelFuture lastWriteFuture;
	private IrcClientHandler ircClientHandler;

	/**
	 * Constructs a new IrcSocket from the given ClientBootstrap
	 * 
	 * @param server
	 *            The internet socket address to connect to.
	 */
	public IrcSocket(ClientBootstrap bootstrap, InetSocketAddress server) {
		this.bootstrap = bootstrap;
		this.server = server;

		ircClientHandler = new IrcClientHandler();
		bootstrap.getPipeline().addLast("handler", ircClientHandler);
	}

	/**
	 * Connects to the setup server.
	 * 
	 * @throws IOException
	 *             If we couldn't connect.
	 */
	public void connect() throws IOException {
		ChannelFuture future = bootstrap.connect(server);
		channel = future.awaitUninterruptibly().getChannel();
		if (!future.isSuccess()) {
			close();
			throw new IOException(future.getCause());
		}
	}

	/**
	 * Returns whether we are connected to the remote server.
	 * 
	 * @return Whether we're connected.
	 */
	public boolean isConnected() {
		return channel.isConnected();
	}

	/**
	 * Sends a message to the server.
	 * 
	 * @param message
	 *            The message to send.
	 */
	public void send(String message) {
		lastWriteFuture = channel.write(message + "\r\n");
	}

	/**
	 * Closes this socket. This blocks until the socket connection is completely
	 * closed.
	 */
	public void close() {
		channel.disconnect();
		
		channel.getCloseFuture().awaitUninterruptibly();

		if (lastWriteFuture != null) {
			lastWriteFuture.awaitUninterruptibly();
		}

		channel.close().awaitUninterruptibly();
	}

	/**
	 * Adds an object that wants to receive events from the Socket class.
	 * 
	 * @param listener
	 *            The listener to add.
	 */
	public void addListener(Listener listener) {
		ircClientHandler.addListener(listener);
	}

	/**
	 * Removes the listener object that was added.
	 * 
	 * @param listener
	 *            The listener to remove.
	 */
	public void removeListener(Listener listener) {
		ircClientHandler.removeListener(listener);
	}

	/**
	 * Listener interface for events generated by the Socket class.
	 */
	public interface Listener {
		/**
		 * Gets called when the socket successfully connected.
		 */
		void onConnect();

		/**
		 * Gets called when a single line was received. The line does not
		 * include line endings.
		 * 
		 * @param line
		 *            The received line.
		 */
		void onReceive(String line);

		/**
		 * Gets called when a single line was sent. The line does not include
		 * line endings.
		 * 
		 * @param line
		 *            The sent line.
		 */
		void onSend(String line);

		/**
		 * Gets called when the socket was disconnected. The disconnect reason
		 * is supplied, which can used to determine whether we want to reconnect
		 * the socket or not.
		 * 
		 * @param reason
		 *            The reason as to why we're disconnected.
		 */
		void onDisconnect(DisconnectReason reason);
	}
}
